<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh-CN" lang="zh-CN">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>

<title>Rails 入门 — Ruby on Rails Guides</title>
<link rel="stylesheet" type="text/css" href="stylesheets/style.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />

<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shCore.css" />
<link rel="stylesheet" type="text/css" href="stylesheets/syntaxhighlighter/shThemeRailsGuides.css" />

<link rel="stylesheet" type="text/css" href="stylesheets/fixes.css" />

<link href="images/favicon.ico" rel="shortcut icon" type="image/x-icon" />
</head>
<body class="guide">
  <div id="topNav">
    <div class="wrapper">
      <strong class="more-info-label">更多内容 <a href="http://rubyonrails.org/">rubyonrails.org:</a> </strong>
      <span class="red-button more-info-button">
        更多内容
      </span>
      <ul class="more-info-links s-hidden">
        <li class="more-info"><a href="http://weblog.rubyonrails.org/">博客</a></li>
        <li class="more-info"><a href="http://guides.rubyonrails.org/">指南</a></li>
        <li class="more-info"><a href="http://api.rubyonrails.org/">API</a></li>
        <li class="more-info"><a href="http://stackoverflow.com/questions/tagged/ruby-on-rails">提问</a></li>
        <li class="more-info"><a href="https://github.com/rails/rails">到 GitHub 贡献</a></li>
        <li class="more-info"><a href="https://ruby-china.org/">Ruby China 社区</a></li>
      </ul>
    </div>
  </div>
  <div id="header">
    <div class="wrapper clearfix">
      <h1><a href="index.html" title="返回首页">Rails 指南</a></h1>
      <ul class="nav">
        <li><a class="nav-item" href="index.html">首页</a></li>
        <li class="guides-index guides-index-large">
          <a href="index.html" id="guidesMenu" class="guides-index-item nav-item">指南索引</a>
          <div id="guides" class="clearfix" style="display: none;">
            <hr />
              <dl class="L">
                <dt>新手入门</dt>
                <dd><a href="getting_started.html">Rails 入门</a></dd>
                <dt>模型</dt>
                <dd><a href="active_record_basics.html">Active Record 基础</a></dd>
                <dd><a href="active_record_migrations.html">Active Record 迁移</a></dd>
                <dd><a href="active_record_validations.html">Active Record 数据验证</a></dd>
                <dd><a href="active_record_callbacks.html">Active Record 回调</a></dd>
                <dd><a href="association_basics.html">Active Record 关联</a></dd>
                <dd><a href="active_record_querying.html">Active Record 查询接口</a></dd>
                <dt>视图</dt>
                <dd><a href="layouts_and_rendering.html">Rails 布局和视图渲染</a></dd>
                <dd><a href="form_helpers.html">Action View 表单辅助方法</a></dd>
                <dt>控制器</dt>
                <dd><a href="action_controller_overview.html">Action Controller 概览</a></dd>
                <dd><a href="routing.html">Rails 路由全解</a></dd>
              </dl>
              <dl class="R">
                <dt>深入探索</dt>
                <dd><a href="active_support_core_extensions.html">Active Support 核心扩展</a></dd>
                <dd><a href="i18n.html">Rails 国际化 API</a></dd>
                <dd><a href="action_mailer_basics.html">Action Mailer 基础</a></dd>
                <dd><a href="active_job_basics.html">Active Job 基础</a></dd>
                <dd><a href="testing.html">Rails 应用测试指南</a></dd>
                <dd><a href="security.html">Ruby on Rails 安全指南</a></dd>
                <dd><a href="debugging_rails_applications.html">调试 Rails 应用</a></dd>
                <dd><a href="configuring.html">配置 Rails 应用</a></dd>
                <dd><a href="command_line.html">Rails 命令行</a></dd>
                <dd><a href="asset_pipeline.html">Asset Pipeline</a></dd>
                <dd><a href="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</a></dd>
                <dd><a href="autoloading_and_reloading_constants.html">自动加载和重新加载常量</a></dd>
                <dd><a href="caching_with_rails.html">Rails 缓存概览</a></dd>
                <dd><a href="api_app.html">使用 Rails 开发只提供 API 的应用</a></dd>
                <dd><a href="action_cable_overview.html">Action Cable 概览</a></dd>
                <dt>扩展 Rails</dt>
                <dd><a href="rails_on_rack.html">Rails on Rack</a></dd>
                <dd><a href="generators.html">创建及定制 Rails 生成器和模板</a></dd>
                <dt>为 Ruby on Rails 做贡献</dt>
                <dd><a href="contributing_to_ruby_on_rails.html">为 Ruby on Rails 做贡献</a></dd>
                <dd><a href="api_documentation_guidelines.html">API 文档指导方针</a></dd>
                <dd><a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南指导方针</a></dd>
                <dt>维护方针</dt>
                <dd><a href="maintenance_policy.html">Ruby on Rails 的维护方针</a></dd>
                <dt>发布记</dt>
                <dd><a href="upgrading_ruby_on_rails.html">Ruby on Rails 升级指南</a></dd>
                <dd><a href="5_0_release_notes.html">Ruby on Rails 5.0 发布记</a></dd>
                <dd><a href="4_2_release_notes.html">Ruby on Rails 4.2 发布记</a></dd>
                <dd><a href="4_1_release_notes.html">Ruby on Rails 4.1 发布记</a></dd>
                <dd><a href="4_0_release_notes.html">Ruby on Rails 4.0 发布记</a></dd>
                <dd><a href="3_2_release_notes.html">Ruby on Rails 3.2 发布记</a></dd>
                <dd><a href="3_1_release_notes.html">Ruby on Rails 3.1 发布记</a></dd>
                <dd><a href="3_0_release_notes.html">Ruby on Rails 3.0 发布记</a></dd>
                <dd><a href="2_3_release_notes.html">Ruby on Rails 2.3 发布记</a></dd>
                <dd><a href="2_2_release_notes.html">Ruby on Rails 2.2 发布记</a></dd>
              </dl>
          </div>
        </li>
        <li><a class="nav-item" href="contributing_to_ruby_on_rails.html">贡献</a></li>
        <li><a class="nav-item" href="credits.html">感谢</a></li>
        <li class="guides-index guides-index-small">
          <select class="guides-index-item nav-item">
            <option value="index.html">指南索引</option>
              <optgroup label="新手入门">
                  <option value="getting_started.html">Rails 入门</option>
              </optgroup>
              <optgroup label="模型">
                  <option value="active_record_basics.html">Active Record 基础</option>
                  <option value="active_record_migrations.html">Active Record 迁移</option>
                  <option value="active_record_validations.html">Active Record 数据验证</option>
                  <option value="active_record_callbacks.html">Active Record 回调</option>
                  <option value="association_basics.html">Active Record 关联</option>
                  <option value="active_record_querying.html">Active Record 查询接口</option>
              </optgroup>
              <optgroup label="视图">
                  <option value="layouts_and_rendering.html">Rails 布局和视图渲染</option>
                  <option value="form_helpers.html">Action View 表单辅助方法</option>
              </optgroup>
              <optgroup label="控制器">
                  <option value="action_controller_overview.html">Action Controller 概览</option>
                  <option value="routing.html">Rails 路由全解</option>
              </optgroup>
              <optgroup label="深入探索">
                  <option value="active_support_core_extensions.html">Active Support 核心扩展</option>
                  <option value="i18n.html">Rails 国际化 API</option>
                  <option value="action_mailer_basics.html">Action Mailer 基础</option>
                  <option value="active_job_basics.html">Active Job 基础</option>
                  <option value="testing.html">Rails 应用测试指南</option>
                  <option value="security.html">Ruby on Rails 安全指南</option>
                  <option value="debugging_rails_applications.html">调试 Rails 应用</option>
                  <option value="configuring.html">配置 Rails 应用</option>
                  <option value="command_line.html">Rails 命令行</option>
                  <option value="asset_pipeline.html">Asset Pipeline</option>
                  <option value="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</option>
                  <option value="autoloading_and_reloading_constants.html">自动加载和重新加载常量</option>
                  <option value="caching_with_rails.html">Rails 缓存概览</option>
                  <option value="api_app.html">使用 Rails 开发只提供 API 的应用</option>
                  <option value="action_cable_overview.html">Action Cable 概览</option>
              </optgroup>
              <optgroup label="扩展 Rails">
                  <option value="rails_on_rack.html">Rails on Rack</option>
                  <option value="generators.html">创建及定制 Rails 生成器和模板</option>
              </optgroup>
              <optgroup label="为 Ruby on Rails 做贡献">
                  <option value="contributing_to_ruby_on_rails.html">为 Ruby on Rails 做贡献</option>
                  <option value="api_documentation_guidelines.html">API 文档指导方针</option>
                  <option value="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南指导方针</option>
              </optgroup>
              <optgroup label="维护方针">
                  <option value="maintenance_policy.html">Ruby on Rails 的维护方针</option>
              </optgroup>
              <optgroup label="发布记">
                  <option value="upgrading_ruby_on_rails.html">Ruby on Rails 升级指南</option>
                  <option value="5_0_release_notes.html">Ruby on Rails 5.0 发布记</option>
                  <option value="4_2_release_notes.html">Ruby on Rails 4.2 发布记</option>
                  <option value="4_1_release_notes.html">Ruby on Rails 4.1 发布记</option>
                  <option value="4_0_release_notes.html">Ruby on Rails 4.0 发布记</option>
                  <option value="3_2_release_notes.html">Ruby on Rails 3.2 发布记</option>
                  <option value="3_1_release_notes.html">Ruby on Rails 3.1 发布记</option>
                  <option value="3_0_release_notes.html">Ruby on Rails 3.0 发布记</option>
                  <option value="2_3_release_notes.html">Ruby on Rails 2.3 发布记</option>
                  <option value="2_2_release_notes.html">Ruby on Rails 2.2 发布记</option>
              </optgroup>
          </select>
        </li>
      </ul>
    </div>
  </div>
  <hr class="hide" />

  <div id="feature">
    <div class="wrapper">
      <h2>Rails 入门</h2><p>本文介绍如何开始使用 Ruby on Rails。</p><p>读完本文后，您将学到：</p>
<ul>
<li>  如何安装 Rails、创建 Rails 应用，如何连接数据库；</li>
<li>  Rails 应用的基本文件结构；</li>
<li>  MVC（模型、视图、控制器）和 REST 架构的基本原理；</li>
<li>  如何快速生成 Rails 应用骨架。</li>
</ul>


              <div id="subCol">
          <h3 class="chapter"><img src="images/chapters_icon.gif" alt="" />目录</h3>
          <ol class="chapters">
<li><a href="#guide-assumptions">前提条件</a></li>
<li><a href="#what-is-rails">Rails 是什么？</a></li>
<li>
<a href="#creating-a-new-rails-project">创建 Rails 项目</a>

<ul>
<li><a href="#installing-rails">安装 Rails</a></li>
<li><a href="#creating-the-blog-application">创建 Blog 应用</a></li>
</ul>
</li>
<li>
<a href="#hello-rails">Hello, Rails!</a>

<ul>
<li><a href="#starting-up-the-web-server">启动 Web 服务器</a></li>
<li><a href="#say-hello-rails">显示“Hello, Rails!”</a></li>
<li><a href="#setting-the-application-home-page">设置应用主页</a></li>
</ul>
</li>
<li>
<a href="#getting-up-and-running">启动并运行起来</a>

<ul>
<li><a href="#laying-down-the-ground-work">打地基</a></li>
<li><a href="#the-first-form">第一个表单</a></li>
<li><a href="#creating-articles">创建文章</a></li>
<li><a href="#creating-the-article-model">创建 Article 模型</a></li>
<li><a href="#running-a-migration">运行迁移</a></li>
<li><a href="#saving-data-in-the-controller">在控制器中保存数据</a></li>
<li><a href="#showing-articles">显示文章</a></li>
<li><a href="#listing-all-articles">列出所有文章</a></li>
<li><a href="#adding-links">添加链接</a></li>
<li><a href="#adding-some-validation">添加验证</a></li>
<li><a href="#updating-articles">更新文章</a></li>
<li><a href="#using-partials-to-clean-up-duplication-in-views">使用局部视图去掉视图中的重复代码</a></li>
<li><a href="#deleting-articles">删除文章</a></li>
</ul>
</li>
<li>
<a href="#adding-a-second-model">添加第二个模型</a>

<ul>
<li><a href="#generating-a-model">生成模型</a></li>
<li><a href="#associating-models">模型关联</a></li>
<li><a href="#adding-a-route-for-comments">为评论添加路由</a></li>
<li><a href="#generating-a-controller">生成控制器</a></li>
</ul>
</li>
<li>
<a href="#refactoring">重构</a>

<ul>
<li><a href="#rendering-partial-collections">渲染局部视图集合</a></li>
<li><a href="#rendering-a-partial-form">渲染局部视图表单</a></li>
</ul>
</li>
<li>
<a href="#deleting-comments">删除评论</a>

<ul>
<li><a href="#deleting-associated-objects">删除关联对象</a></li>
</ul>
</li>
<li>
<a href="#security">安全</a>

<ul>
<li><a href="#basic-authentication">基本身份验证</a></li>
<li><a href="#other-security-considerations">其他安全注意事项</a></li>
</ul>
</li>
<li><a href="#whats-next">接下来做什么？</a></li>
<li><a href="#configuration-gotchas">配置问题</a></li>
</ol>

        </div>

    </div>
  </div>

  <div id="container">
    <div class="wrapper">
      <div id="mainCol">
        <p><a class="anchor" id="guide-assumptions"></a></p><h3 id="guide-assumptions">1 前提条件</h3><p>本文针对想从零开始开发 Rails 应用的初学者，不要求 Rails 使用经验。不过，为了能顺利阅读，还是需要事先安装好一些软件：</p>
<ul>
<li>  <a href="https://www.ruby-lang.org/en/downloads">Ruby</a> 2.2.2 及以上版本</li>
<li>  <a href="http://rubyinstaller.org/downloads/">开发工具包</a>的正确版本（针对 Windows 用户）</li>
<li>  包管理工具 <a href="https://rubygems.org/">RubyGems</a>，随 Ruby 预装。若想深入了解 RubyGems，请参阅 <a href="http://guides.rubygems.org/">RubyGems 指南</a>
</li>
<li>  <a href="https://www.sqlite.org/">SQLite3 数据库</a>
</li>
</ul>
<p>Rails 是使用 Ruby 语言开发的 Web 应用框架。如果之前没接触过 Ruby，会感到直接学习 Rails 的学习曲线很陡。这里提供几个学习 Ruby 的在线资源：</p>
<ul>
<li>  <a href="https://www.ruby-lang.org/en/documentation/">Ruby 语言官方网站</a>
</li>
<li>  <a href="https://github.com/vhf/free-programming-books/blob/master/free-programming-books.md#ruby">免费编程图书列表</a>
</li>
</ul>
<p>需要注意的是，有些资源虽然很好，但针对的是 Ruby 1.8 甚至 1.6 这些老版本，因此不涉及一些 Rails 日常开发的常见句法。</p><p><a class="anchor" id="what-is-rails"></a></p><h3 id="what-is-rails">2 Rails 是什么？</h3><p>Rails 是使用 Ruby 语言编写的 Web 应用开发框架，目的是通过解决快速开发中的共通问题，简化 Web 应用的开发。与其他编程语言和框架相比，使用 Rails 只需编写更少代码就能实现更多功能。有经验的 Rails 程序员常说，Rails 让 Web 应用开发变得更有趣。</p><p>Rails 有自己的设计原则，认为问题总有最好的解决方法，并且有意识地通过设计来鼓励用户使用最好的解决方法，而不是其他替代方案。一旦掌握了“Rails 之道”，就可能获得生产力的巨大提升。在 Rails 开发中，如果不改变使用其他编程语言时养成的习惯，总想使用原有的设计模式，开发体验可能就不那么让人愉快了。</p><p>Rails 哲学包含两大指导思想：</p>
<ul>
<li>  不要自我重复（DRY）： DRY 是软件开发中的一个原则，意思是“系统中的每个功能都要具有单一、准确、可信的实现。”。不重复表述同一件事，写出的代码才更易维护、更具扩展性，也更不容易出问题。</li>
<li>  多约定，少配置： Rails 为 Web 应用的大多数需求都提供了最好的解决方法，并且默认使用这些约定，而不是在长长的配置文件中设置每个细节。</li>
</ul>
<p><a class="anchor" id="creating-a-new-rails-project"></a></p><h3 id="creating-a-new-rails-project">3 创建 Rails 项目</h3><p>阅读本文的最佳方法是一步步跟着操作。所有这些步骤对于运行示例应用都是必不可少的，同时也不需要更多的代码或步骤。</p><p>通过学习本文，你将学会如何创建一个名为 Blog 的 Rails 项目，这是一个非常简单的博客。在动手开发之前，请确保已经安装了 Rails。</p><div class="info"><p>文中的示例代码使用 UNIX 风格的命令行提示符 $，如果你的命令行提示符是自定义的，看起来可能会不一样。在 Windows 中，命令行提示符可能类似 <code>c:\source_code&gt;</code>。</p></div><p><a class="anchor" id="installing-rails"></a></p><h4 id="installing-rails">3.1 安装 Rails</h4><p>打开命令行：在 macOS 中打开 Terminal.app，在 Windows 中要在开始菜单中选择“运行”，然后输入“cmd.exe”。本文中所有以 $ 开头的代码，都应该在命令行中执行。首先确认是否安装了 Ruby 的最新版本：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ ruby -v
ruby 2.3.1p112

</pre>
</div>
<div class="info"><p>有很多工具可以帮助你快速地在系统中安装 Ruby 和 Ruby on Rails。Windows 用户可以使用 <a href="http://railsinstaller.org/">Rails Installer</a>，macOS 用户可以使用 <a href="https://github.com/tokaido/tokaidoapp">Tokaido</a>。更多操作系统中的安装方法请访问 <a href="https://www.ruby-lang.org/en/documentation/installation/">ruby-lang.org</a>。</p></div><p>很多类 UNIX 系统都预装了版本较新的 SQLite3。在 Windows 中，通过 Rails Installer 安装 Rails 会同时安装 SQLite3。其他操作系统中 SQLite3 的安装方法请参阅 <a href="https://www.sqlite.org/">SQLite3 官网</a>。接下来，确认 SQLite3 是否在 PATH 中：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ sqlite3 --version

</pre>
</div>
<p>执行结果应该显示 SQLite3 的版本号。</p><p>安装 Rails，请使用 RubyGems 提供的 <code>gem install</code> 命令：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ gem install rails

</pre>
</div>
<p>执行下面的命令来确认所有软件是否都已正确安装：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails --version

</pre>
</div>
<p>如果执行结果类似 <code>Rails 5.1.0</code>，那么就可以继续往下读了。</p><p><a class="anchor" id="creating-the-blog-application"></a></p><h4 id="creating-the-blog-application">3.2 创建 Blog 应用</h4><p>Rails 提供了许多名为生成器（generator）的脚本，这些脚本可以为特定任务生成所需的全部文件，从而简化开发。其中包括新应用生成器，这个脚本用于创建 Rails 应用骨架，避免了手动编写基础代码。</p><p>要使用新应用生成器，请打开终端，进入具有写权限的文件夹，输入：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ rails new blog

</pre>
</div>
<p>这个命令会在文件夹 <code>blog</code> 中创建名为 Blog 的 Rails 应用，然后执行 <code>bundle install</code> 命令安装 Gemfile 中列出的 gem 及其依赖。</p><div class="info"><p>执行 <code>rails new -h</code> 命令可以查看新应用生成器的所有命令行选项。</p></div><p>创建 blog 应用后，进入该文件夹：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ cd blog

</pre>
</div>
<p><code>blog</code> 文件夹中有许多自动生成的文件和文件夹，这些文件和文件夹组成了 Rails 应用的结构。本文涉及的大部分工作都在 app 文件夹中完成。下面简单介绍一下这些用新应用生成器默认选项生成的文件和文件夹的功能：</p>
<table>
<thead>
<tr>
<th>文件/文件夹</th>
<th>作用</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>app/</code></td>
<td>包含应用的控制器、模型、视图、辅助方法、邮件程序、频道、作业和静态资源文件。这个文件夹是本文剩余内容关注的重点。</td>
</tr>
<tr>
<td><code>bin/</code></td>
<td>包含用于启动应用的 rails 脚本，以及用于安装、更新、部署或运行应用的其他脚本。</td>
</tr>
<tr>
<td><code>config/</code></td>
<td>配置应用的路由、数据库等。详情请参阅<a href="configuring.html">配置 Rails 应用</a>。</td>
</tr>
<tr>
<td><code>config.ru</code></td>
<td>基于 Rack 的服务器所需的 Rack 配置，用于启动应用。</td>
</tr>
<tr>
<td><code>db/</code></td>
<td>包含当前数据库的模式，以及数据库迁移文件。</td>
</tr>
<tr>
<td>
<code>Gemfile</code>, <code>Gemfile.lock</code>
</td>
<td>这两个文件用于指定 Rails 应用所需的 gem 依赖。Bundler gem 需要用到这两个文件。关于 Bundler 的更多介绍，请访问 <a href="http://bundler.io/">Bundler 官网</a>。</td>
</tr>
<tr>
<td><code>lib/</code></td>
<td>应用的扩展模块。</td>
</tr>
<tr>
<td><code>log/</code></td>
<td>应用日志文件。</td>
</tr>
<tr>
<td><code>public/</code></td>
<td>仅有的可以直接从外部访问的文件夹，包含静态文件和编译后的静态资源文件。</td>
</tr>
<tr>
<td><code>Rakefile</code></td>
<td>定位并加载可在命令行中执行的任务。这些任务在 Rails 的各个组件中定义。如果要添加自定义任务，请不要修改 Rakefile，直接把自定义任务保存在 <code>lib/tasks</code> 文件夹中即可。</td>
</tr>
<tr>
<td><code>README.md</code></td>
<td>应用的自述文件，说明应用的用途、安装方法等。</td>
</tr>
<tr>
<td><code>test/</code></td>
<td>单元测试、固件和其他测试装置。详情请参阅<a href="testing.html">Rails 应用测试指南</a>。</td>
</tr>
<tr>
<td><code>tmp/</code></td>
<td>临时文件（如缓存和 PID 文件）。</td>
</tr>
<tr>
<td><code>vendor/</code></td>
<td>包含第三方代码，如第三方 gem。</td>
</tr>
<tr>
<td><code>.gitignore</code></td>
<td>告诉 Git 要忽略的文件（或模式）。详情参见 <a href="https://help.github.com/articles/ignoring-files">GitHub 帮助文档</a>。</td>
</tr>
</tbody>
</table>
<p><a class="anchor" id="hello-rails"></a></p><h3 id="hello-rails">4 Hello, Rails!</h3><p>首先，让我们快速地在页面中添加一些文字。为了访问页面，需要运行 Rails 应用服务器（即 Web 服务器）。</p><p><a class="anchor" id="starting-up-the-web-server"></a></p><h4 id="starting-up-the-web-server">4.1 启动 Web 服务器</h4><p>实际上这个 Rails 应用已经可以正常运行了。要访问应用，需要在开发设备中启动 Web 服务器。请在 <code>blog</code> 文件夹中执行下面的命令：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails server

</pre>
</div>
<div class="info"><p>Windows 用户需要把 <code>bin</code> 文件夹下的脚本文件直接传递给 Ruby 解析器，例如 <code>ruby bin\rails server</code>。</p></div><div class="info"><p>编译 CoffeeScript 和压缩 JavaScript 静态资源文件需要 JavaScript 运行时，如果没有运行时，在压缩静态资源文件时会报错，提示没有 <code>execjs</code>。macOS 和 Windows 一般都提供了 JavaScript 运行时。在 Rails 应用的 Gemfile 中，<code>therubyracer</code> gem 被注释掉了，如果需要使用这个 gem，请去掉注释。对于 JRuby 用户，推荐使用 <code>therubyrhino</code> 这个运行时，在 JRuby 中创建 Rails 应用的 Gemfile 中默认包含了这个 gem。要查看 Rails 支持的所有运行时，请参阅 <a href="https://github.com/rails/execjs#readme">ExecJS</a>。</p></div><p>上述命令会启动 Puma，这是 Rails 默认使用的 Web 服务器。要查看运行中的应用，请打开浏览器窗口，访问 <a href="http://localhost:3000">http://localhost:3000</a>。这时应该看到默认的 Rails 欢迎页面：</p><p><img src="images/getting_started/rails_welcome.png" alt="默认的 Rails 欢迎页面"></p><div class="info"><p>要停止 Web 服务器，请在终端中按 Ctrl+C 键。服务器停止后命令行提示符会重新出现。在大多数类 Unix 系统中，包括 macOS，命令行提示符是 $ 符号。在开发模式中，一般情况下无需重启服务器，服务器会自动加载修改后的文件。</p></div><p>欢迎页面是创建 Rails 应用的冒烟测试，看到这个页面就表示应用已经正确配置，能够正常工作了。</p><p><a class="anchor" id="say-hello-rails"></a></p><h4 id="say-hello-rails">4.2 显示“Hello, Rails!”</h4><p>要让 Rails 显示“Hello, Rails!”，需要创建控制器和视图。</p><p>控制器接受向应用发起的特定访问请求。路由决定哪些访问请求被哪些控制器接收。一般情况下，一个控制器会对应多个路由，不同路由对应不同动作。动作搜集数据并把数据提供给视图。</p><p>视图以人类能看懂的格式显示数据。有一点要特别注意，数据是在控制器而不是视图中获取的，视图只是显示数据。默认情况下，视图模板使用 eRuby（嵌入式 Ruby）语言编写，经由 Rails 解析后，再发送给用户。</p><p>可以用控制器生成器来创建控制器。下面的命令告诉控制器生成器创建一个包含“index”动作的“Welcome”控制器：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails generate controller Welcome index

</pre>
</div>
<p>上述命令让 Rails 生成了多个文件和一个路由：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
create  app/controllers/welcome_controller.rb
 route  get 'welcome/index'
invoke  erb
create    app/views/welcome
create    app/views/welcome/index.html.erb
invoke  test_unit
create    test/controllers/welcome_controller_test.rb
invoke  helper
create    app/helpers/welcome_helper.rb
invoke    test_unit
invoke  assets
invoke    coffee
create      app/assets/javascripts/welcome.coffee
invoke    scss
create      app/assets/stylesheets/welcome.scss

</pre>
</div>
<p>其中最重要的文件是控制器和视图，控制器位于 <code>app/controllers/welcome_controller.rb</code> 文件 ，视图位于 <code>app/views/welcome/index.html.erb</code> 文件 。</p><p>在文本编辑器中打开 <code>app/views/welcome/index.html.erb</code> 文件，删除所有代码，然后添加下面的代码：</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
&lt;h1&gt;Hello, Rails!&lt;/h1&gt;

</pre>
</div>
<p><a class="anchor" id="setting-the-application-home-page"></a></p><h4 id="setting-the-application-home-page">4.3 设置应用主页</h4><p>现在我们已经创建了控制器和视图，还需要告诉 Rails 何时显示“Hello, Rails!”，我们希望在访问根地址  <a href="http://localhost:3000">http://localhost:3000</a> 时显示。目前根地址显示的还是默认的 Rails 欢迎页面。</p><p>接下来需要告诉 Rails 真正的主页在哪里。</p><p>在编辑器中打开 <code>config/routes.rb</code> 文件。</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Rails.application.routes.draw do
  get 'welcome/index'

  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

</pre>
</div>
<p>这是应用的路由文件，使用特殊的 DSL（Domain-Specific Language，领域专属语言）编写，告诉 Rails 把访问请求发往哪个控制器和动作。编辑这个文件，添加一行代码 <code>root 'welcome#index'</code>，此时文件内容应该变成下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Rails.application.routes.draw do
  get 'welcome/index'

  root 'welcome#index'
end

</pre>
</div>
<p><code>root 'welcome#index'</code> 告诉 Rails 对根路径的访问请求应该发往 welcome 控制器的 index 动作，<code>get 'welcome/index'</code> 告诉 Rails 对 <a href="http://localhost:3000/welcome/index">http://localhost:3000/welcome/index</a> 的访问请求应该发往 welcome 控制器的 index 动作。后者是之前使用控制器生成器创建控制器（<code>bin/rails generate controller Welcome index</code>）时自动生成的。</p><p>如果在生成控制器时停止了服务器，请再次启动服务器（<code>bin/rails server</code>），然后在浏览器中访问  <a href="http://localhost:3000">http://localhost:3000</a>。我们会看到之前添加到 <code>app/views/welcome/index.html.erb</code> 文件 的“Hello, Rails!”信息，这说明新定义的路由确实把访问请求发往了 <code>WelcomeController</code> 的 <code>index</code> 动作，并正确渲染了视图。</p><div class="info"><p>关于路由的更多介绍，请参阅<a href="routing.html">Rails 路由全解</a>。</p></div><p><a class="anchor" id="getting-up-and-running"></a></p><h3 id="getting-up-and-running">5 启动并运行起来</h3><p>前文已经介绍了如何创建控制器、动作和视图，接下来我们要创建一些更具实用价值的东西。</p><p>在 Blog 应用中创建一个资源（resource）。资源是一个术语，表示一系列类似对象的集合，如文章、人或动物。资源中的项目可以被创建、读取、更新和删除，这些操作简称 CRUD（Create, Read, Update, Delete）。</p><p>Rails 提供了 <code>resources</code> 方法，用于声明标准的 REST 资源。把 articles 资源添加到 <code>config/routes.rb</code> 文件，此时文件内容应该变成下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
Rails.application.routes.draw do
  get 'welcome/index'

  resources :articles

  root 'welcome#index'
end

</pre>
</div>
<p>执行 <code>bin/rails routes</code> 命令，可以看到所有标准 REST 动作都具有对应的路由。输出结果中各列的意义稍后会作说明，现在只需注意  Rails 从 article 的单数形式推导出了它的复数形式，并进行了合理使用。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails routes
      Prefix Verb   URI Pattern                  Controller#Action
    articles GET    /articles(.:format)          articles#index
             POST   /articles(.:format)          articles#create
 new_article GET    /articles/new(.:format)      articles#new
edit_article GET    /articles/:id/edit(.:format) articles#edit
     article GET    /articles/:id(.:format)      articles#show
             PATCH  /articles/:id(.:format)      articles#update
             PUT    /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy
        root GET    /                            welcome#index

</pre>
</div>
<p>下一节，我们将为应用添加新建文章和查看文章的功能。这两个操作分别对应于 CRUD 的“C”和“R”：创建和读取。下面是用于新建文章的表单：</p><p><img src="images/getting_started/new_article.png" alt="用于新建文章的表单"></p><p>表单看起来很简陋，不过没关系，之后我们再来美化。</p><p><a class="anchor" id="laying-down-the-ground-work"></a></p><h4 id="laying-down-the-ground-work">5.1 打地基</h4><p>首先，应用需要一个页面用于新建文章，<code>/articles/new</code> 是个不错的选择。相关路由之前已经定义过了，可以直接访问。打开  <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>，会看到下面的路由错误：</p><p><img src="images/getting_started/routing_error_no_controller.png" alt="路由错误，常量 ArticlesController 未初始化"></p><p>产生错误的原因是，用于处理该请求的控制器还没有定义。解决问题的方法很简单：创建 <code>Articles</code> 控制器。执行下面的命令：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails generate controller Articles

</pre>
</div>
<p>打开刚刚生成的 <code>app/controllers/articles_controller.rb</code> 文件，会看到一个空的控制器：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController &lt; ApplicationController
end

</pre>
</div>
<p>控制器实际上只是一个继承自 <code>ApplicationController</code> 的类。接在来要在这个类中定义的方法也就是控制器的动作。这些动作对文章执行 CRUD 操作。</p><div class="note"><p>在 Ruby 中，有 <code>public</code>、<code>private</code> 和 <code>protected</code> 三种方法，其中只有 <code>public</code> 方法才能作为控制器的动作。详情请参阅 <a href="http://www.ruby-doc.org/docs/ProgrammingRuby/">Programming Ruby</a> 一书。</p></div><p>现在刷新 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>，会看到一个新错误：</p><p><img src="images/getting_started/unknown_action_new_for_articles.png" alt="未知动作，在 ArticlesController 中找不到 new 动作"></p><p>这个错误的意思是，Rails 在刚刚生成的 <code>ArticlesController</code> 中找不到 new 动作。这是因为在 Rails 中生成控制器时，如果不指定想要的动作，生成的控制器就会是空的。</p><p>在控制器中手动定义动作，只需要定义一个新方法。打开 <code>app/controllers/articles_controller.rb</code> 文件，在 <code>ArticlesController</code> 类中定义 <code>new</code> 方法，此时控制器应该变成下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController &lt; ApplicationController
  def new
  end
end

</pre>
</div>
<p>在 <code>ArticlesController</code> 中定义 <code>new</code> 方法后，再次刷新 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>，会看到另一个错误：</p><p><img src="images/getting_started/template_is_missing_articles_new.png" alt="未知格式，缺少对应模板"></p><p>产生错误的原因是，Rails 要求这样的常规动作有用于显示数据的对应视图。如果没有视图可用，Rails 就会抛出异常。</p><p>上图中下面的几行都被截断了，下面是完整信息：</p>
<blockquote>
<p>ArticlesController#new is missing a template for this request format and variant. request.formats: ["text/html"] request.variant: [] NOTE! For XHR/Ajax or API requests, this action would normally respond with 204 No Content: an empty white screen. Since you’re loading it in a web browser, we assume that you expected to actually render a template, not… nothing, so we’re showing an error to be extra-clear. If you expect 204 No Content, carry on. That’s what you’ll get from an XHR or API request. Give it a shot.</p>
</blockquote>
<p>内容还真不少！让我们快速浏览一下，看看各部分是什么意思。</p><p>第一部分说明缺少哪个模板，这里缺少的是 <code>articles/new</code> 模板。Rails 首先查找这个模板，如果找不到再查找 <code>application/new</code> 模板。之所以会查找后面这个模板，是因为 <code>ArticlesController</code> 继承自 <code>ApplicationController</code>。</p><p>下一部分是 <code>request.formats</code>，说明响应使用的模板格式。当我们在浏览器中请求页面时，<code>request.formats</code> 的值是 <code>text/html</code>，因此 Rails 会查找 HTML 模板。<code>request.variant</code> 指明伺服的是何种物理设备，帮助 Rails 判断该使用哪个模板渲染响应。它的值是空的，因为没有为其提供信息。</p><p>在本例中，能够工作的最简单的模板位于 <code>app/views/articles/new.html.erb</code> 文件中。文件的扩展名很重要：第一个扩展名是模板格式，第二个扩展名是模板处理器。Rails 会尝试在 <code>app/views</code> 文件夹中查找 <code>articles/new</code> 模板。这个模板的格式只能是 <code>html</code>，模板处理器只能是 <code>erb</code>、<code>builder</code> 和 <code>coffee</code> 中的一个。<code>:erb</code> 是最常用的 HTML 模板处理器，<code>:builder</code> 是 XML 模板处理器，<code>:coffee</code> 模板处理器用 CoffeeScript 创建 JavaScript 模板。因为我们要创建 HTML 表单，所以应该使用能够在 HTML 中嵌入 Ruby 的 <code>ERB</code> 语言。</p><p>所以我们需要创建 <code>articles/new.html.erb</code> 文件，并把它放在应用的 <code>app/views</code> 文件夹中。</p><p>现在让我们继续前进。新建 <code>app/views/articles/new.html.erb</code> 文件，添加下面的代码：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;h1&gt;New Article&lt;/h1&gt;

</pre>
</div>
<p>刷新 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>，会看到页面有了标题。现在路由、控制器、动作和视图都可以协调地工作了！是时候创建用于新建文章的表单了。</p><p><a class="anchor" id="the-first-form"></a></p><h4 id="the-first-form">5.2 第一个表单</h4><p>在模板中创建表单，可以使用表单构建器。Rails 中最常用的表单构建器是 <code>form_for</code> 辅助方法。让我们使用这个方法，在 <code>app/views/articles/new.html.erb</code> 文件中添加下面的代码：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;%= form_for :article do |f| %&gt;
  &lt;p&gt;
    &lt;%= f.label :title %&gt;&lt;br&gt;
    &lt;%= f.text_field :title %&gt;
  &lt;/p&gt;

  &lt;p&gt;
    &lt;%= f.label :text %&gt;&lt;br&gt;
    &lt;%= f.text_area :text %&gt;
  &lt;/p&gt;

  &lt;p&gt;
    &lt;%= f.submit %&gt;
  &lt;/p&gt;
&lt;% end %&gt;

</pre>
</div>
<p>现在刷新页面，会看到和前文截图一样的表单。在 Rails 中创建表单就是这么简单！</p><p>调用 <code>form_for</code> 辅助方法时，需要为表单传递一个标识对象作为参数，这里是 <code>:article</code> 符号。这个符号告诉 <code>form_for</code> 辅助方法表单用于处理哪个对象。在 <code>form_for</code> 辅助方法的块中，<code>f</code> 表示 <code>FormBuilder</code> 对象，用于创建两个标签和两个文本字段，分别用于添加文章的标题和正文。最后在 <code>f</code> 对象上调用 <code>submit</code> 方法来为表单创建提交按钮。</p><p>不过这个表单还有一个问题，查看 HTML 源代码会看到表单 <code>action</code> 属性的值是 <code>/articles/new</code>，指向的是当前页面，而当前页面只是用于显示新建文章的表单。</p><p>应该把表单指向其他 URL，为此可以使用 <code>form_for</code> 辅助方法的 <code>:url</code> 选项。在 Rails 中习惯用 <code>create</code> 动作来处理提交的表单，因此应该把表单指向这个动作。</p><p>修改 <code>app/views/articles/new.html.erb</code> 文件的 <code>form_for</code> 这一行，改为：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;%= form_for :article, url: articles_path do |f| %&gt;

</pre>
</div>
<p>这里我们把 <code>articles_path</code> 辅助方法传递给 <code>:url</code> 选项。要想知道这个方法有什么用，我们可以回过头看一下 <code>bin/rails routes</code> 的输出结果：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails routes
      Prefix Verb   URI Pattern                  Controller#Action
    articles GET    /articles(.:format)          articles#index
             POST   /articles(.:format)          articles#create
 new_article GET    /articles/new(.:format)      articles#new
edit_article GET    /articles/:id/edit(.:format) articles#edit
     article GET    /articles/:id(.:format)      articles#show
             PATCH  /articles/:id(.:format)      articles#update
             PUT    /articles/:id(.:format)      articles#update
             DELETE /articles/:id(.:format)      articles#destroy
        root GET    /                            welcome#index

</pre>
</div>
<p><code>articles_path</code> 辅助方法告诉 Rails 把表单指向和 <code>articles</code> 前缀相关联的 URI 模式。默认情况下，表单会向这个路由发起 <code>POST</code> 请求。这个路由和当前控制器 <code>ArticlesController</code> 的 <code>create</code> 动作相关联。</p><p>有了表单和与之相关联的路由，我们现在可以填写表单，然后点击提交按钮来新建文章了，请实际操作一下。提交表单后，会看到一个熟悉的错误：</p><p><img src="images/getting_started/unknown_action_create_for_articles.png" alt="未知动作，在 `ArticlesController` 中找不到 `create` 动作"></p><p>解决问题的方法是在 <code>ArticlesController</code> 中创建 <code>create</code> 动作。</p><p><a class="anchor" id="creating-articles"></a></p><h4 id="creating-articles">5.3 创建文章</h4><p>要消除“未知动作”错误，我们需要修改 <code>app/controllers/articles_controller.rb</code> 文件，在 <code>ArticlesController</code> 类的 <code>new</code> 动作之后添加 <code>create</code> 动作，就像下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController &lt; ApplicationController
  def new
  end

  def create
  end
end

</pre>
</div>
<p>现在重新提交表单，会看到什么都没有改变。别着急！这是因为当我们没有说明动作的响应是什么时，Rails 默认返回 <code>204 No Content response</code>。我们刚刚添加了 <code>create</code> 动作，但没有说明响应是什么。这里，<code>create</code> 动作应该把新建文章保存到数据库中。</p><p>表单提交后，其字段以参数形式传递给 Rails，然后就可以在控制器动作中引用这些参数，以执行特定任务。要想查看这些参数的内容，可以把 <code>create</code> 动作的代码修改成下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def create
  render plain: params[:article].inspect
end

</pre>
</div>
<p>这里 <code>render</code> 方法接受了一个简单的散列（hash）作为参数，<code>:plain</code> 键的值是 <code>params[:article].inspect</code>。<code>params</code> 方法是代表表单提交的参数（或字段）的对象。<code>params</code> 方法返回 <code>ActionController::Parameters</code> 对象，这个对象允许使用字符串或符号访问散列的键。这里我们只关注通过表单提交的参数。</p><div class="info"><p>请确保牢固掌握 <code>params</code> 方法，这个方法很常用。让我们看一个示例 URL：<code>http://www.example.com/?username=dhh&amp;email=dhh@email.com</code>。在这个 URL 中，<code>params[:username]</code> 的值是“dhh”，<code>params[:email]</code> 的值是“<code>dhh@email.com</code>”。</p></div><p>如果再次提交表单，会看到下面这些内容：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
&lt;ActionController::Parameters {"title"=&gt;"First Article!", "text"=&gt;"This is my first article."} permitted: false&gt;

</pre>
</div>
<p><code>create</code> 动作把表单提交的参数都显示出来了，但这并没有什么用，只是看到了参数实际上却什么也没做。</p><p><a class="anchor" id="creating-the-article-model"></a></p><h4 id="creating-the-article-model">5.4 创建 Article 模型</h4><p>在 Rails 中，模型使用单数名称，对应的数据库表使用复数名称。Rails 提供了用于创建模型的生成器，大多数 Rails 开发者在新建模型时倾向于使用这个生成器。要想新建模型，请执行下面的命令：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails generate model Article title:string text:text

</pre>
</div>
<p>上面的命令告诉 Rails 创建 <code>Article</code> 模型，并使模型具有字符串类型的 <code>title</code> 属性和文本类型的 <code>text</code> 属性。这两个属性会自动添加到数据库的 <code>articles</code> 表中，并映射到 <code>Article</code> 模型上。</p><p>为此 Rails 会创建一堆文件。这里我们只关注 <code>app/models/article.rb</code> 和 <code>db/migrate/20140120191729_create_articles.rb</code> 这两个文件 （后面这个文件名和你看到的可能会有点不一样）。后者负责创建数据库结构，下一节会详细说明。</p><div class="info"><p>Active Record 很智能，能自动把数据表的字段名映射到模型属性上，因此无需在 Rails 模型中声明属性，让 Active Record 自动完成即可。</p></div><p><a class="anchor" id="running-a-migration"></a></p><h4 id="running-a-migration">5.5 运行迁移</h4><p>如前文所述，<code>bin/rails generate model</code> 命令会在 <code>db/migrate</code> 文件夹中生成数据库迁移文件。迁移是用于简化创建和修改数据库表操作的 Ruby 类。Rails 使用 rake 命令运行迁移，并且在迁移作用于数据库之后还可以撤销迁移操作。迁移的文件名包含了时间戳，以确保迁移按照创建时间顺序运行。</p><p>让我们看一下 <code>db/migrate/YYYYMMDDHHMMSS_create_articles.rb</code> 文件（记住，你的文件名可能会有点不一样），会看到下面的内容：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateArticles &lt; ActiveRecord::Migration[5.0]
  def change
    create_table :articles do |t|
      t.string :title
      t.text :text

      t.timestamps
    end
  end
end

</pre>
</div>
<p>上面的迁移创建了 <code>change</code> 方法，在运行迁移时会调用这个方法。在 <code>change</code> 方法中定义的操作都是可逆的，在需要时 Rails 知道如何撤销这些操作。运行迁移后会创建 <code>articles</code> 表，这个表包括一个字符串字段和一个文本字段，以及两个用于跟踪文章创建和更新时间的时间戳字段。</p><div class="info"><p>关于迁移的更多介绍，请参阅<a href="active_record_migrations.html">Active Record 迁移</a>。</p></div><p>现在可以使用 <code>bin/rails</code> 命令运行迁移了：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails db:migrate

</pre>
</div>
<p>Rails 会执行迁移命令并告诉我们它创建了 Articles 表。</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
==  CreateArticles: migrating ==================================================
-- create_table(:articles)
   -&gt; 0.0019s
==  CreateArticles: migrated (0.0020s) =========================================

</pre>
</div>
<div class="note"><p>因为默认情况下我们是在开发环境中工作，所以上述命令应用于 <code>config/database.yml</code> 文件中 <code>development</code> 部分定义的的数据库。要想在其他环境中执行迁移，例如生产环境，就必须在调用命令时显式传递环境变量：<code>bin/rails db:migrate RAILS_ENV=production</code>。</p></div><p><a class="anchor" id="saving-data-in-the-controller"></a></p><h4 id="saving-data-in-the-controller">5.6 在控制器中保存数据</h4><p>回到 <code>ArticlesController</code>，修改 <code>create</code> 动作，使用新建的 <code>Article</code> 模型把数据保存到数据库。打开 <code>app/controllers/articles_controller.rb</code> 文件，像下面这样修改 <code>create</code> 动作：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def create
  @article = Article.new(params[:article])

  @article.save
  redirect_to @article
end

</pre>
</div>
<p>让我们看一下上面的代码都做了什么：Rails 模型可以用相应的属性初始化，它们会自动映射到对应的数据库字段。<code>create</code> 动作中的第一行代码完成的就是这个操作（记住，<code>params[:article]</code> 包含了我们想要的属性）。接下来 <code>@article.save</code> 负责把模型保存到数据库。最后把页面重定向到 <code>show</code> 动作，这个 <code>show</code> 动作我们稍后再定义。</p><div class="info"><p>你可能想知道，为什么在上面的代码中 <code>Article.new</code> 的 <code>A</code> 是大写的，而在本文的其他地方引用 articles 时大都是小写的。因为这里我们引用的是在 <code>app/models/article.rb</code> 文件中定义的 <code>Article</code> 类，而在 Ruby 中类名必须以大写字母开头。</p></div><div class="info"><p>之后我们会看到，<code>@article.save</code> 返回布尔值，以表明文章是否保存成功。</p></div><p>现在访问 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>，我们就快要能够创建文章了，但我们还会看到下面的错误：</p><p><img src="images/getting_started/forbidden_attributes_for_new_article.png" alt="禁用属性错误"></p><p>Rails 提供了多种安全特性来帮助我们编写安全的应用，上面看到的就是一种安全特性。这个安全特性叫做 <a href="action_controller_overview.html#strong-parameters">健壮参数</a>（strong parameter），要求我们明确地告诉 Rails 哪些参数允许在控制器动作中使用。</p><p>为什么我们要这样自找麻烦呢？一次性获取所有控制器参数并自动赋值给模型显然更简单，但这样做会造成恶意使用的风险。设想一下，如果有人对服务器发起了一个精心设计的请求，看起来就像提交了一篇新文章，但同时包含了能够破坏应用完整性的额外字段和值，会怎么样？这些恶意数据会批量赋值给模型，然后和正常数据一起进入数据库，这样就有可能破坏我们的应用或者造成更大损失。</p><p>所以我们只能为控制器参数设置白名单，以避免错误地批量赋值。这里，我们想在 <code>create</code> 动作中合法使用 <code>title</code> 和 <code>text</code> 参数，为此需要使用 <code>require</code> 和 <code>permit</code> 方法。像下面这样修改 <code>create</code> 动作中的一行代码：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
@article = Article.new(params.require(:article).permit(:title, :text))

</pre>
</div>
<p>上述代码通常被抽象为控制器类的一个方法，以便在控制器的多个动作中重用，例如在 <code>create</code> 和 <code>update</code> 动作中都会用到。除了批量赋值问题，为了禁止从外部调用这个方法，通常还要把它设置为 <code>private</code>。最后的代码像下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def create
  @article = Article.new(article_params)

  @article.save
  redirect_to @article
end

private
  def article_params
    params.require(:article).permit(:title, :text)
  end

</pre>
</div>
<div class="info"><p>关于键壮参数的更多介绍，请参阅上面提供的参考资料和<a href="http://weblog.rubyonrails.org/2012/3/21/strong-parameters/">这篇博客</a>。</p></div><p><a class="anchor" id="showing-articles"></a></p><h4 id="showing-articles">5.7 显示文章</h4><p>现在再次提交表单，Rails 会提示找不到 <code>show</code> 动作。尽管这个提示没有多大用处，但在继续前进之前我们还是先添加 <code>show</code> 动作吧。</p><p>之前我们在 <code>bin/rails routes</code> 命令的输出结果中看到，<code>show</code> 动作对应的路由是：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
article GET    /articles/:id(.:format)      articles#show

</pre>
</div>
<p>特殊句法 <code>:id</code> 告诉 Rails 这个路由期望接受 <code>:id</code> 参数，在这里也就是文章的 ID。</p><p>和前面一样，我们需要在 <code>app/controllers/articles_controller.rb</code> 文件中添加 <code>show</code> 动作，并创建对应的视图文件。</p><div class="note"><p>常见的做法是按照以下顺序在控制器中放置标准的 CRUD 动作：<code>index</code>，<code>show</code>，<code>new</code>，<code>edit</code>，<code>create</code>，<code>update</code> 和 <code>destroy</code>。你也可以按照自己的顺序放置这些动作，但要记住它们都是公开方法，如前文所述，必须放在私有方法之前才能正常工作。</p></div><p>有鉴于此，让我们像下面这样添加 <code>show</code> 动作：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController &lt; ApplicationController
  def show
    @article = Article.find(params[:id])
  end

  def new
  end

  # 为了行文简洁，省略以下内容

</pre>
</div>
<p>上面的代码中有几个问题需要注意。我们使用 <code>Article.find</code> 来查找文章，并传入 <code>params[:id]</code> 以便从请求中获得 <code>:id</code> 参数。我们还使用实例变量（前缀为 <code>@</code>）保存对文章对象的引用。这样做是因为 Rails 会把所有实例变量传递给视图。</p><p>现在新建 <code>app/views/articles/show.html.erb</code> 文件，添加下面的代码：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;p&gt;
  &lt;strong&gt;Title:&lt;/strong&gt;
  &lt;%= @article.title %&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Text:&lt;/strong&gt;
  &lt;%= @article.text %&gt;
&lt;/p&gt;

</pre>
</div>
<p>通过上面的修改，我们终于能够新建文章了。访问 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>，自己试一试吧！</p><p><img src="images/getting_started/show_action_for_articles.png" alt="显示文章"></p><p><a class="anchor" id="listing-all-articles"></a></p><h4 id="listing-all-articles">5.8 列出所有文章</h4><p>我们还需要列出所有文章，下面就来完成这个功能。在 <code>bin/rails routes</code> 命令的输出结果中，和列出文章对应的路由是：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
articles GET    /articles(.:format)          articles#index

</pre>
</div>
<p>在 <code>app/controllers/articles_controller.rb</code> 文件的 <code>ArticlesController</code> 中为上述路由添加对应的 <code>index</code> 动作。在编写 <code>index</code> 动作时，常见的做法是把它作为控制器的第一个方法，就像下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController &lt; ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
  end

  # 为了行文简洁，省略以下内容

</pre>
</div>
<p>最后，在 <code>app/views/articles/index.html.erb</code> 文件中为 <code>index</code> 动作添加视图：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;h1&gt;Listing articles&lt;/h1&gt;

&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;Title&lt;/th&gt;
    &lt;th&gt;Text&lt;/th&gt;
  &lt;/tr&gt;

  &lt;% @articles.each do |article| %&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;%= article.title %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= article.text %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= link_to 'Show', article_path(article) %&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;% end %&gt;
&lt;/table&gt;

</pre>
</div>
<p>现在访问 <a href="http://localhost:3000/articles">http://localhost:3000/articles</a>，会看到已创建的所有文章的列表。</p><p><a class="anchor" id="adding-links"></a></p><h4 id="adding-links">5.9 添加链接</h4><p>至此，我们可以创建、显示、列出文章了。下面我们添加一些指向这些页面的链接。</p><p>打开 <code>app/views/welcome/index.html.erb</code> 文件，修改成下面这样：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;h1&gt;Hello, Rails!&lt;/h1&gt;
&lt;%= link_to 'My Blog', controller: 'articles' %&gt;

</pre>
</div>
<p><code>link_to</code> 方法是 Rails 内置的视图辅助方法之一，用于创建基于链接文本和地址的超链接。在这里地址指的是文章列表页面的路径。</p><p>接下来添加指向其他视图的链接。首先在 <code>app/views/articles/index.html.erb</code> 文件中添加“New Article”链接，把这个链接放在 <code>&lt;table&gt;</code> 标签之前：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;%= link_to 'New article', new_article_path %&gt;

</pre>
</div>
<p>点击这个链接会打开用于新建文章的表单。</p><p>接下来在 <code>app/views/articles/new.html.erb</code> 文件中添加返回 <code>index</code> 动作的链接，把这个链接放在表单之后：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;%= form_for :article, url: articles_path do |f| %&gt;
  ...
&lt;% end %&gt;

&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p>最后，在 <code>app/views/articles/show.html.erb</code> 模板中添加返回 <code>index</code> 动作的链接，这样用户看完一篇文章后就可以返回文章列表页面了：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;p&gt;
  &lt;strong&gt;Title:&lt;/strong&gt;
  &lt;%= @article.title %&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Text:&lt;/strong&gt;
  &lt;%= @article.text %&gt;
&lt;/p&gt;

&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<div class="info"><p>链接到当前控制器的动作时不需要指定 <code>:controller</code> 选项，因为 Rails 默认使用当前控制器。</p></div><div class="info"><p>在开发环境中（默认情况下我们是在开发环境中工作），Rails 针对每个浏览器请求都会重新加载应用，因此对应用进行修改之后不需要重启服务器。</p></div><p><a class="anchor" id="adding-some-validation"></a></p><h4 id="adding-some-validation">5.10 添加验证</h4><p><code>app/models/article.rb</code> 模型文件简单到只有两行代码：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Article &lt; ApplicationRecord
end

</pre>
</div>
<p>虽然这个文件中代码很少，但请注意 <code>Article</code> 类继承自 <code>ApplicationRecord</code> 类，而 <code>ApplicationRecord</code> 类继承自 <code>ActiveRecord::Base</code> 类。正是 <code>ActiveRecord::Base</code> 类为 Rails 模型提供了大量功能，包括基本的数据库 CRUD 操作（创建、读取、更新、删除）、数据验证，以及对复杂搜索的支持和关联多个模型的能力。</p><p>Rails 提供了许多方法用于验证传入模型的数据。打开 <code>app/models/article.rb</code> 文件，像下面这样修改：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Article &lt; ApplicationRecord
  validates :title, presence: true,
                    length: { minimum: 5 }
end

</pre>
</div>
<p>添加的代码用于确保每篇文章都有标题，并且标题长度不少于 5 个字符。在 Rails 模型中可以验证多种条件，包括字段是否存在、字段是否唯一、字段的格式、关联对象是否存在，等等。关于验证的更多介绍，请参阅<a href="active_record_validations.html">Active Record 数据验证</a>。</p><p>现在验证已经添加完毕，如果我们在调用 <code>@article.save</code> 时传递了无效的文章数据，验证就会返回 <code>false</code>。再次打开 <code>app/controllers/articles_controller.rb</code> 文件，会看到我们并没有在 <code>create</code> 动作中检查 <code>@article.save</code> 的调用结果。在这里如果 <code>@article.save</code> 失败了，就需要把表单再次显示给用户。为此，需要像下面这样修改 <code>app/controllers/articles_controller.rb</code> 文件中的 <code>new</code> 和 <code>create</code> 动作：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def new
  @article = Article.new
end

def create
  @article = Article.new(article_params)

  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

private
  def article_params
    params.require(:article).permit(:title, :text)
  end

</pre>
</div>
<p>在上面的代码中，我们在 <code>new</code> 动作中创建了新的实例变量 <code>@article</code>，稍后你就会知道为什么要这样做。</p><p>注意在 <code>create</code> 动作中，当 <code>save</code> 返回 <code>false</code> 时，我们用 <code>render</code> 代替了 <code>redirect_to</code>。使用 <code>render</code> 方法是为了把 <code>@article</code> 对象回传给 <code>new</code> 模板。这里渲染操作是在提交表单的这个请求中完成的，而 <code>redirect_to</code> 会告诉浏览器发起另一个请求。</p><p>刷新 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>，试着提交一篇没有标题的文章，Rails 会返回这个表单，但这种处理方式没有多大用处，更好的做法是告诉用户哪里出错了。为此需要修改 <code>app/views/articles/new.html.erb</code> 文件，添加显示错误信息的代码：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;%= form_for :article, url: articles_path do |f| %&gt;

  &lt;% if @article.errors.any? %&gt;
    &lt;div id="error_explanation"&gt;
      &lt;h2&gt;
        &lt;%= pluralize(@article.errors.count, "error") %&gt; prohibited
        this article from being saved:
      &lt;/h2&gt;
      &lt;ul&gt;
        &lt;% @article.errors.full_messages.each do |msg| %&gt;
          &lt;li&gt;&lt;%= msg %&gt;&lt;/li&gt;
        &lt;% end %&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;% end %&gt;

  &lt;p&gt;
    &lt;%= f.label :title %&gt;&lt;br&gt;
    &lt;%= f.text_field :title %&gt;
  &lt;/p&gt;

  &lt;p&gt;
    &lt;%= f.label :text %&gt;&lt;br&gt;
    &lt;%= f.text_area :text %&gt;
  &lt;/p&gt;

  &lt;p&gt;
    &lt;%= f.submit %&gt;
  &lt;/p&gt;

&lt;% end %&gt;

&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p>上面我们添加了一些代码。我们使用 <code>@article.errors.any?</code> 检查是否有错误，如果有错误就使用 <code>@article.errors.full_messages</code> 列出所有错误信息。</p><p><code>pluralize</code> 是 Rails 提供的辅助方法，接受一个数字和一个字符串作为参数。如果数字比 1 大，字符串会被自动转换为复数形式。</p><p>在 <code>ArticlesController</code> 中添加 <code>@article = Article.new</code> 是因为如果不这样做，在视图中 <code>@article</code> 的值就会是 <code>nil</code>，这样在调用 <code>@article.errors.any?</code> 时就会抛出错误。</p><div class="info"><p>Rails 会自动用 div 包围含有错误信息的字段，并为这些 div 添加 <code>field_with_errors</code> 类。我们可以定义 CSS 规则突出显示错误信息。</p></div><p>当我们再次访问 <a href="http://localhost:3000/articles/new">http://localhost:3000/articles/new</a>，试着提交一篇没有标题的文章，就会看到友好的错误信息。</p><p><img src="images/getting_started/form_with_errors.png" alt="出错的表单"></p><p><a class="anchor" id="updating-articles"></a></p><h4 id="updating-articles">5.11 更新文章</h4><p>我们已经介绍了 CRUD 操作中的“CR”两种操作，下面让我们看一下“U”操作，也就是更新文章。</p><p>第一步要在 <code>ArticlesController</code> 中添加 <code>edit</code> 动作，通常把这个动作放在 <code>new</code> 动作和 <code>create</code> 动作之间，就像下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def new
  @article = Article.new
end

def edit
  @article = Article.find(params[:id])
end

def create
  @article = Article.new(article_params)

  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

</pre>
</div>
<p>接下来在视图中添加一个表单，这个表单类似于前文用于新建文章的表单。创建 <code>app/views/articles/edit.html.erb</code> 文件，添加下面的代码：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;h1&gt;Edit article&lt;/h1&gt;

&lt;%= form_for(@article) do |f| %&gt;

  &lt;% if @article.errors.any? %&gt;
    &lt;div id="error_explanation"&gt;
      &lt;h2&gt;
        &lt;%= pluralize(@article.errors.count, "error") %&gt; prohibited
        this article from being saved:
      &lt;/h2&gt;
      &lt;ul&gt;
        &lt;% @article.errors.full_messages.each do |msg| %&gt;
          &lt;li&gt;&lt;%= msg %&gt;&lt;/li&gt;
        &lt;% end %&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;% end %&gt;

  &lt;p&gt;
    &lt;%= f.label :title %&gt;&lt;br&gt;
    &lt;%= f.text_field :title %&gt;
  &lt;/p&gt;

  &lt;p&gt;
    &lt;%= f.label :text %&gt;&lt;br&gt;
    &lt;%= f.text_area :text %&gt;
  &lt;/p&gt;

  &lt;p&gt;
    &lt;%= f.submit %&gt;
  &lt;/p&gt;

&lt;% end %&gt;

&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p>上面的代码把表单指向了 <code>update</code> 动作，这个动作稍后我们再来定义。</p><p>传入 <code>@article</code> 对象后，会自动为表单创建 URL，用于提交编辑后的文章。</p><p><code>method: :patch</code> 选项告诉 Rails 使用 <code>PATCH</code> 方法提交表单。根据 REST 协议，<code>PATCH</code> 方法是<strong>更新</strong>资源时使用的 HTTP 方法。</p><p><code>form_for</code> 辅助方法的第一个参数可以是对象，例如 <code>@article</code>，<code>form_for</code> 辅助方法会用这个对象的字段来填充表单。如果传入和实例变量（<code>@article</code>）同名的符号（<code>:article</code>），也会自动产生相同效果，上面的代码使用的就是符号。关于 <code>form_for</code> 辅助方法参数的更多介绍，请参阅 <a href="http://api.rubyonrails.org/v5.1.1/classes/ActionView/Helpers/FormHelper.html#method-i-form_for"><code>form_for</code> 的文档</a>。</p><p>接下来在 <code>app/controllers/articles_controller.rb</code> 文件中创建 <code>update</code> 动作，把这个动作放在 <code>create</code> 动作和 <code>private</code> 方法之间：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def create
  @article = Article.new(article_params)

  if @article.save
    redirect_to @article
  else
    render 'new'
  end
end

def update
  @article = Article.find(params[:id])

  if @article.update(article_params)
    redirect_to @article
  else
    render 'edit'
  end
end

private
  def article_params
    params.require(:article).permit(:title, :text)
  end

</pre>
</div>
<p><code>update</code> 动作用于更新已有记录，它接受一个散列作为参数，散列中包含想要更新的属性。和之前一样，如果更新文章时发生错误，就需要把表单再次显示给用户。</p><p>上面的代码重用了之前为 <code>create</code> 动作定义的 <code>article_params</code> 方法。</p><div class="info"><p>不用把所有属性都传递给 <code>update</code> 方法。例如，调用 <code>@article.update(title: 'A new title')</code> 时，Rails 只更新 <code>title</code> 属性而不修改其他属性。</p></div><p>最后，我们想在文章列表中显示指向 <code>edit</code> 动作的链接。打开 <code>app/views/articles/index.html.erb</code> 文件，在 <code>Show</code> 链接后面添加 <code>Edit</code> 链接：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;Title&lt;/th&gt;
    &lt;th&gt;Text&lt;/th&gt;
    &lt;th colspan="2"&gt;&lt;/th&gt;
  &lt;/tr&gt;

  &lt;% @articles.each do |article| %&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;%= article.title %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= article.text %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= link_to 'Show', article_path(article) %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= link_to 'Edit', edit_article_path(article) %&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;% end %&gt;
&lt;/table&gt;

</pre>
</div>
<p>接着在 <code>app/views/articles/show.html.erb</code> 模板中添加 <code>Edit</code> 链接，这样文章页面也有 <code>Edit</code> 链接了。把这个链接添加到模板底部：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
...

&lt;%= link_to 'Edit', edit_article_path(@article) %&gt; |
&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p>下面是文章列表现在的样子：</p><p><img src="images/getting_started/index_action_with_edit_link.png" alt="文章列表"></p><p><a class="anchor" id="using-partials-to-clean-up-duplication-in-views"></a></p><h4 id="using-partials-to-clean-up-duplication-in-views">5.12 使用局部视图去掉视图中的重复代码</h4><p>编辑文章页面和新建文章页面看起来很相似，实际上这两个页面用于显示表单的代码是相同的。现在我们要用局部视图来去掉这些重复代码。按照约定，局部视图的文件名以下划线开头。</p><div class="info"><p>关于局部视图的更多介绍，请参阅<a href="layouts_and_rendering.html">Rails 布局和视图渲染</a>。</p></div><p>新建 <code>app/views/articles/_form.html.erb</code> 文件，添加下面的代码：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;%= form_for @article do |f| %&gt;

  &lt;% if @article.errors.any? %&gt;
    &lt;div id="error_explanation"&gt;
      &lt;h2&gt;
        &lt;%= pluralize(@article.errors.count, "error") %&gt; prohibited
        this article from being saved:
      &lt;/h2&gt;
      &lt;ul&gt;
        &lt;% @article.errors.full_messages.each do |msg| %&gt;
          &lt;li&gt;&lt;%= msg %&gt;&lt;/li&gt;
        &lt;% end %&gt;
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;% end %&gt;

  &lt;p&gt;
    &lt;%= f.label :title %&gt;&lt;br&gt;
    &lt;%= f.text_field :title %&gt;
  &lt;/p&gt;

  &lt;p&gt;
    &lt;%= f.label :text %&gt;&lt;br&gt;
    &lt;%= f.text_area :text %&gt;
  &lt;/p&gt;

  &lt;p&gt;
    &lt;%= f.submit %&gt;
  &lt;/p&gt;

&lt;% end %&gt;

</pre>
</div>
<p>除了第一行 <code>form_for</code> 的用法变了之外，其他代码都和之前一样。之所以能用这个更短、更简单的 <code>form_for</code> 声明来代替新建文章页面和编辑文章页面的两个表单，是因为 <code>@article</code> 是一个资源，对应于一套 REST 式路由，Rails 能够推断出应该使用哪个地址和方法。关于 <code>form_for</code> 用法的更多介绍，请参阅“<a href="http://api.rubyonrails.org/v5.1.1/classes/ActionView/Helpers/FormHelper.html#method-i-form_for-label-Resource-oriented+style">面向资源的风格</a>”。</p><p>现在更新 <code>app/views/articles/new.html.erb</code> 视图，以使用新建的局部视图。把文件内容替换为下面的代码：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;h1&gt;New article&lt;/h1&gt;

&lt;%= render 'form' %&gt;

&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p>然后按照同样的方法修改 <code>app/views/articles/edit.html.erb</code> 视图：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;h1&gt;Edit article&lt;/h1&gt;

&lt;%= render 'form' %&gt;

&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p><a class="anchor" id="deleting-articles"></a></p><h4 id="deleting-articles">5.13 删除文章</h4><p>现在该介绍 CRUD 中的“D”操作了，也就是从数据库删除文章。按照 REST 架构的约定，在 <code>bin/rails routes</code> 命令的输出结果中删除文章的路由是：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
DELETE /articles/:id(.:format)      articles#destroy

</pre>
</div>
<p>删除资源的路由应该使用 <code>delete</code> 路由方法。如果在删除资源时仍然使用 <code>get</code> 路由，就可能给那些设计恶意地址的人提供可乘之机：</p><div class="code_container">
<pre class="brush: xml; gutter: false; toolbar: false">
&lt;a href='http://example.com/articles/1/destroy'&gt;look at this cat!&lt;/a&gt;

</pre>
</div>
<p>我们用 <code>delete</code> 方法来删除资源，对应的路由会映射到 <code>app/controllers/articles_controller.rb</code> 文件中的 <code>destroy</code> 动作，稍后我们要创建这个动作。<code>destroy</code> 动作是控制器中的最后一个 CRUD 动作，和其他公共 CRUD 动作一样，这个动作应该放在 <code>private</code> 或 <code>protected</code> 方法之前。打开 <code>app/controllers/articles_controller.rb</code> 文件，添加下面的代码：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
def destroy
  @article = Article.find(params[:id])
  @article.destroy

  redirect_to articles_path
end

</pre>
</div>
<p>在 <code>app/controllers/articles_controller.rb</code> 文件中，<code>ArticlesController</code> 的完整代码应该像下面这样：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController &lt; ApplicationController
  def index
    @articles = Article.all
  end

  def show
    @article = Article.find(params[:id])
  end

  def new
    @article = Article.new
  end

  def edit
    @article = Article.find(params[:id])
  end

  def create
    @article = Article.new(article_params)

    if @article.save
      redirect_to @article
    else
      render 'new'
    end
  end

  def update
    @article = Article.find(params[:id])

    if @article.update(article_params)
      redirect_to @article
    else
      render 'edit'
    end
  end

  def destroy
    @article = Article.find(params[:id])
    @article.destroy

    redirect_to articles_path
  end

  private
    def article_params
      params.require(:article).permit(:title, :text)
    end
end

</pre>
</div>
<p>在 Active Record 对象上调用 <code>destroy</code> 方法，就可从数据库中删除它们。注意，我们不需要为 <code>destroy</code> 动作添加视图，因为完成操作后它会重定向到 <code>index</code> 动作。</p><p>最后，在 <code>index</code> 动作的模板（<code>app/views/articles/index.html.erb</code>）中加上“Destroy”链接，这样就大功告成了：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;h1&gt;Listing Articles&lt;/h1&gt;
&lt;%= link_to 'New article', new_article_path %&gt;
&lt;table&gt;
  &lt;tr&gt;
    &lt;th&gt;Title&lt;/th&gt;
    &lt;th&gt;Text&lt;/th&gt;
    &lt;th colspan="3"&gt;&lt;/th&gt;
  &lt;/tr&gt;

  &lt;% @articles.each do |article| %&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;%= article.title %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= article.text %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= link_to 'Show', article_path(article) %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= link_to 'Edit', edit_article_path(article) %&gt;&lt;/td&gt;
      &lt;td&gt;&lt;%= link_to 'Destroy', article_path(article),
              method: :delete,
              data: { confirm: 'Are you sure?' } %&gt;&lt;/td&gt;
    &lt;/tr&gt;
  &lt;% end %&gt;
&lt;/table&gt;

</pre>
</div>
<p>在上面的代码中，<code>link_to</code> 辅助方法生成“Destroy”链接的用法有点不同，其中第二个参数是具名路由（named route），还有一些选项作为其他参数。<code>method: :delete</code> 和 <code>data: { confirm: 'Are you sure?' }</code> 选项用于设置链接的 HTML5 属性，这样点击链接后 Rails 会先向用户显示一个确认对话框，然后用 <code>delete</code> 方法发起请求。这些操作是通过 JavaScript 脚本 <code>jquery_ujs</code> 实现的，这个脚本在生成应用骨架时已经被自动包含在了应用的布局中（<code>app/views/layouts/application.html.erb</code>）。如果没有这个脚本，确认对话框就无法显示。</p><p><img src="images/getting_started/confirm_dialog.png" alt="确认对话框"></p><div class="info"><p>关于 jQuery 非侵入式适配器（jQuery UJS）的更多介绍，请参阅<a href="working_with_javascript_in_rails.html">在 Rails 中使用 JavaScript</a>。</p></div><p>恭喜你！现在你已经可以创建、显示、列出、更新和删除文章了！</p><div class="info"><p>通常 Rails 鼓励用资源对象来代替手动声明路由。关于路由的更多介绍，请参阅<a href="routing.html">Rails 路由全解</a>。</p></div><p><a class="anchor" id="adding-a-second-model"></a></p><h3 id="adding-a-second-model">6 添加第二个模型</h3><p>现在是为应用添加第二个模型的时候了。这个模型用于处理文章评论。</p><p><a class="anchor" id="generating-a-model"></a></p><h4 id="generating-a-model">6.1 生成模型</h4><p>接下来将要使用的生成器，和之前用于创建 <code>Article</code> 模型的一样。这次我们要创建 <code>Comment</code> 模型，用于保存文章评论。在终端中执行下面的命令：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails generate model Comment commenter:string body:text article:references

</pre>
</div>
<p>上面的命令会生成 4 个文件：</p>
<table>
<thead>
<tr>
<th>文件</th>
<th>用途</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>db/migrate/20140120201010_create_comments.rb</code></td>
<td>用于在数据库中创建 comments 表的迁移文件（你的文件名会包含不同的时间戳）</td>
</tr>
<tr>
<td><code>app/models/comment.rb</code></td>
<td>
<code>Comment</code> 模型文件</td>
</tr>
<tr>
<td><code>test/models/comment_test.rb</code></td>
<td>
<code>Comment</code> 模型的测试文件</td>
</tr>
<tr>
<td><code>test/fixtures/comments.yml</code></td>
<td>用于测试的示例评论</td>
</tr>
</tbody>
</table>
<p>首先看一下 <code>app/models/comment.rb</code> 文件：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Comment &lt; ApplicationRecord
  belongs_to :article
end

</pre>
</div>
<p>可以看到，<code>Comment</code> 模型文件的内容和之前的 <code>Article</code> 模型差不多，仅仅多了一行 <code>belongs_to :article</code>，这行代码用于建立 Active Record 关联。下一节会简单介绍关联。</p><p>在上面的 Bash 命令中使用的 <code>:references</code> 关键字是一种特殊的模型数据类型，用于在数据表中新建字段。这个字段以提供的模型名加上 <code>_id</code> 后缀作为字段名，保存整数值。之后通过分析 <code>db/schema.rb</code> 文件可以更好地理解这些内容。</p><p>除了模型文件，Rails 还生成了迁移文件，用于创建对应的数据表：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CreateComments &lt; ActiveRecord::Migration[5.0]
  def change
    create_table :comments do |t|
      t.string :commenter
      t.text :body
      t.references :article, foreign_key: true

      t.timestamps
    end
  end
end

</pre>
</div>
<p><code>t.references</code> 这行代码创建 <code>article_id</code> 整数字段，为这个字段建立索引，并建立指向 <code>articles</code> 表的 <code>id</code> 字段的外键约束。下面运行这个迁移：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails db:migrate

</pre>
</div>
<p>Rails 很智能，只会运行针对当前数据库还没有运行过的迁移，运行结果像下面这样：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
==  CreateComments: migrating =================================================
-- create_table(:comments)
   -&gt; 0.0115s
==  CreateComments: migrated (0.0119s) ========================================

</pre>
</div>
<p><a class="anchor" id="associating-models"></a></p><h4 id="associating-models">6.2 模型关联</h4><p>Active Record 关联让我们可以轻易地声明两个模型之间的关系。对于评论和文章，我们可以像下面这样声明：</p>
<ul>
<li>  每一条评论都属于某一篇文章</li>
<li>  一篇文章可以有多条评论</li>
</ul>
<p>实际上，这种表达方式和 Rails 用于声明模型关联的句法非常接近。前文我们已经看过 <code>Comment</code> 模型中用于声明模型关联的代码，这行代码用于声明每一条评论都属于某一篇文章：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Comment &lt; ApplicationRecord
  belongs_to :article
end

</pre>
</div>
<p>现在修改 <code>app/models/article.rb</code> 文件来添加模型关联的另一端：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Article &lt; ApplicationRecord
  has_many :comments
  validates :title, presence: true,
                    length: { minimum: 5 }
end

</pre>
</div>
<p>这两行声明能够启用一些自动行为。例如，如果 <code>@article</code> 实例变量表示一篇文章，就可以使用 <code>@article.comments</code> 以数组形式取回这篇文章的所有评论。</p><div class="info"><p>关于模型关联的更多介绍，请参阅<a href="association_basics.html">Active Record 关联</a>。</p></div><p><a class="anchor" id="adding-a-route-for-comments"></a></p><h4 id="adding-a-route-for-comments">6.3 为评论添加路由</h4><p>和 <code>welcome</code> 控制器一样，在添加路由之后 Rails 才知道在哪个地址上查看评论。再次打开 <code>config/routes.rb</code> 文件，像下面这样进行修改：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
resources :articles do
  resources :comments
end

</pre>
</div>
<p>上面的代码在 <code>articles</code> 资源中创建 <code>comments</code> 资源，这种方式被称为嵌套资源。这是表明文章和评论之间层级关系的另一种方式。</p><div class="info"><p>关于路由的更多介绍，请参阅<a href="routing.html">Rails 路由全解</a>。</p></div><p><a class="anchor" id="generating-a-controller"></a></p><h4 id="generating-a-controller">6.4 生成控制器</h4><p>有了模型，下面应该创建对应的控制器了。还是使用前面用过的生成器：</p><div class="code_container">
<pre class="brush: plain; gutter: false; toolbar: false">
$ bin/rails generate controller Comments

</pre>
</div>
<p>上面的命令会创建 5 个文件和一个空文件夹：</p>
<table>
<thead>
<tr>
<th>文件/文件夹</th>
<th>用途</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>app/controllers/comments_controller.rb</code></td>
<td>Comments 控制器文件</td>
</tr>
<tr>
<td><code>app/views/comments/</code></td>
<td>控制器的视图保存在这里</td>
</tr>
<tr>
<td><code>test/controllers/comments_controller_test.rb</code></td>
<td>控制器的测试文件</td>
</tr>
<tr>
<td><code>app/helpers/comments_helper.rb</code></td>
<td>视图辅助方法文件</td>
</tr>
<tr>
<td><code>app/assets/javascripts/comments.coffee</code></td>
<td>控制器的 CoffeeScript 文件</td>
</tr>
<tr>
<td><code>app/assets/stylesheets/comments.scss</code></td>
<td>控制器的样式表文件</td>
</tr>
</tbody>
</table>
<p>在博客中，读者看完文章后可以直接发表评论，并且马上可以看到这些评论是否在页面上显示出来了。我们的博客采取同样的设计。这里 <code>CommentsController</code> 需要提供创建评论和删除垃圾评论的方法。</p><p>首先修改显示文章的模板（<code>app/views/articles/show.html.erb</code>），添加发表评论的功能：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;p&gt;
  &lt;strong&gt;Title:&lt;/strong&gt;
  &lt;%= @article.title %&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Text:&lt;/strong&gt;
  &lt;%= @article.text %&gt;
&lt;/p&gt;

&lt;h2&gt;Add a comment:&lt;/h2&gt;
&lt;%= form_for([@article, @article.comments.build]) do |f| %&gt;
  &lt;p&gt;
    &lt;%= f.label :commenter %&gt;&lt;br&gt;
    &lt;%= f.text_field :commenter %&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;%= f.label :body %&gt;&lt;br&gt;
    &lt;%= f.text_area :body %&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;%= f.submit %&gt;
  &lt;/p&gt;
&lt;% end %&gt;

&lt;%= link_to 'Edit', edit_article_path(@article) %&gt; |
&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p>上面的代码在显示文章的页面中添加了用于新建评论的表单，通过调用 <code>CommentsController</code> 的 <code>create</code> 动作来发表评论。这里 <code>form_for</code> 辅助方法以数组为参数，会创建嵌套路由，例如 <code>/articles/1/comments</code>。</p><p>接下来在 <code>app/controllers/comments_controller.rb</code> 文件中添加 <code>create</code> 动作：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CommentsController &lt; ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

</pre>
</div>
<p>上面的代码比 <code>Articles</code> 控制器的代码复杂得多，这是嵌套带来的副作用。对于每一个发表评论的请求，都必须记录这条评论属于哪篇文章，因此需要在 <code>Article</code> 模型上调用 <code>find</code> 方法来获取文章对象。</p><p>此外，上面的代码还利用了关联特有的方法，在 <code>@article.comments</code> 上调用 <code>create</code> 方法来创建和保存评论，同时自动把评论和对应的文章关联起来。</p><p>添加评论后，我们使用 <code>article_path(@article)</code> 辅助方法把用户带回原来的文章页面。如前文所述，这里调用了 <code>ArticlesController</code> 的 <code>show</code> 动作来渲染 <code>show.html.erb</code> 模板，因此需要修改 <code>app/views/articles/show.html.erb</code> 文件来显示评论：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;p&gt;
  &lt;strong&gt;Title:&lt;/strong&gt;
  &lt;%= @article.title %&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Text:&lt;/strong&gt;
  &lt;%= @article.text %&gt;
&lt;/p&gt;

&lt;h2&gt;Comments&lt;/h2&gt;
&lt;% @article.comments.each do |comment| %&gt;
  &lt;p&gt;
    &lt;strong&gt;Commenter:&lt;/strong&gt;
    &lt;%= comment.commenter %&gt;
  &lt;/p&gt;

  &lt;p&gt;
    &lt;strong&gt;Comment:&lt;/strong&gt;
    &lt;%= comment.body %&gt;
  &lt;/p&gt;
&lt;% end %&gt;

&lt;h2&gt;Add a comment:&lt;/h2&gt;
&lt;%= form_for([@article, @article.comments.build]) do |f| %&gt;
  &lt;p&gt;
    &lt;%= f.label :commenter %&gt;&lt;br&gt;
    &lt;%= f.text_field :commenter %&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;%= f.label :body %&gt;&lt;br&gt;
    &lt;%= f.text_area :body %&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;%= f.submit %&gt;
  &lt;/p&gt;
&lt;% end %&gt;

&lt;%= link_to 'Edit', edit_article_path(@article) %&gt; |
&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p>现在可以在我们的博客中为文章添加评论了，评论添加后就会显示在正确的位置上。</p><p><img src="images/getting_started/article_with_comments.png" alt="带有评论的文章"></p><p><a class="anchor" id="refactoring"></a></p><h3 id="refactoring">7 重构</h3><p>现在博客的文章和评论都已经正常工作，打开 <code>app/views/articles/show.html.erb</code> 文件，会看到文件代码变得又长又不美观。因此下面我们要用局部视图来重构代码。</p><p><a class="anchor" id="rendering-partial-collections"></a></p><h4 id="rendering-partial-collections">7.1 渲染局部视图集合</h4><p>首先创建评论的局部视图，把显示文章评论的代码抽出来。创建 <code>app/views/comments/_comment.html.erb</code> 文件，添加下面的代码：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;p&gt;
  &lt;strong&gt;Commenter:&lt;/strong&gt;
  &lt;%= comment.commenter %&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Comment:&lt;/strong&gt;
  &lt;%= comment.body %&gt;
&lt;/p&gt;

</pre>
</div>
<p>然后像下面这样修改 <code>app/views/articles/show.html.erb</code> 文件：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;p&gt;
  &lt;strong&gt;Title:&lt;/strong&gt;
  &lt;%= @article.title %&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Text:&lt;/strong&gt;
  &lt;%= @article.text %&gt;
&lt;/p&gt;

&lt;h2&gt;Comments&lt;/h2&gt;
&lt;%= render @article.comments %&gt;

&lt;h2&gt;Add a comment:&lt;/h2&gt;
&lt;%= form_for([@article, @article.comments.build]) do |f| %&gt;
  &lt;p&gt;
    &lt;%= f.label :commenter %&gt;&lt;br&gt;
    &lt;%= f.text_field :commenter %&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;%= f.label :body %&gt;&lt;br&gt;
    &lt;%= f.text_area :body %&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;%= f.submit %&gt;
  &lt;/p&gt;
&lt;% end %&gt;

&lt;%= link_to 'Edit', edit_article_path(@article) %&gt; |
&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p>这样对于 <code>@article.comments</code> 集合中的每条评论，都会渲染 <code>app/views/comments/_comment.html.erb</code> 文件中的局部视图。<code>render</code> 方法会遍历 <code>@article.comments</code> 集合，把每条评论赋值给局部视图中的同名局部变量，也就是这里的 <code>comment</code> 变量。</p><p><a class="anchor" id="rendering-a-partial-form"></a></p><h4 id="rendering-a-partial-form">7.2 渲染局部视图表单</h4><p>我们把添加评论的代码也移到局部视图中。创建 <code>app/views/comments/_form.html.erb</code> 文件，添加下面的代码：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;%= form_for([@article, @article.comments.build]) do |f| %&gt;
  &lt;p&gt;
    &lt;%= f.label :commenter %&gt;&lt;br&gt;
    &lt;%= f.text_field :commenter %&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;%= f.label :body %&gt;&lt;br&gt;
    &lt;%= f.text_area :body %&gt;
  &lt;/p&gt;
  &lt;p&gt;
    &lt;%= f.submit %&gt;
  &lt;/p&gt;
&lt;% end %&gt;

</pre>
</div>
<p>然后像下面这样修改 <code>app/views/articles/show.html.erb</code> 文件：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;p&gt;
  &lt;strong&gt;Title:&lt;/strong&gt;
  &lt;%= @article.title %&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Text:&lt;/strong&gt;
  &lt;%= @article.text %&gt;
&lt;/p&gt;

&lt;h2&gt;Comments&lt;/h2&gt;
&lt;%= render @article.comments %&gt;

&lt;h2&gt;Add a comment:&lt;/h2&gt;
&lt;%= render 'comments/form' %&gt;

&lt;%= link_to 'Edit', edit_article_path(@article) %&gt; |
&lt;%= link_to 'Back', articles_path %&gt;

</pre>
</div>
<p>上面的代码中第二个 <code>render</code> 方法的参数就是我们刚刚定义的 <code>comments/form</code> 局部视图。Rails 很智能，能够发现字符串中的斜线，并意识到我们想渲染 <code>app/views/comments</code> 文件夹中的 <code>_form.html.erb</code> 文件。</p><p><code>@article</code> 是实例变量，因此在所有局部视图中都可以使用。</p><p><a class="anchor" id="deleting-comments"></a></p><h3 id="deleting-comments">8 删除评论</h3><p>博客还有一个重要功能是删除垃圾评论。为了实现这个功能，我们需要在视图中添加一个链接，并在 <code>CommentsController</code> 中添加 <code>destroy</code> 动作。</p><p>首先在 <code>app/views/comments/_comment.html.erb</code> 局部视图中添加删除评论的链接：</p><div class="code_container">
<pre class="brush: ruby; html-script: true; gutter: false; toolbar: false">
&lt;p&gt;
  &lt;strong&gt;Commenter:&lt;/strong&gt;
  &lt;%= comment.commenter %&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;strong&gt;Comment:&lt;/strong&gt;
  &lt;%= comment.body %&gt;
&lt;/p&gt;

&lt;p&gt;
  &lt;%= link_to 'Destroy Comment', [comment.article, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %&gt;
&lt;/p&gt;

</pre>
</div>
<p>点击“Destroy Comment”链接后，会向 <code>CommentsController</code> 发起 <code>DELETE /articles/:article_id/comments/:id</code> 请求，这个请求将用于删除指定评论。下面在控制器（<code>app/controllers/comments_controller.rb</code>）中添加 <code>destroy</code> 动作：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CommentsController &lt; ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

</pre>
</div>
<p><code>destroy</code> 动作首先找到指定文章，然后在 <code>@article.comments</code> 集合中找到指定评论，接着从数据库删除这条评论，最后重定向到显示文章的页面。</p><p><a class="anchor" id="deleting-associated-objects"></a></p><h4 id="deleting-associated-objects">8.1 删除关联对象</h4><p>如果要删除一篇文章，文章的相关评论也需要删除，否则这些评论还会占用数据库空间。在 Rails 中可以使用关联的 <code>dependent</code> 选项来完成这一工作。像下面这样修改 <code>app/models/article.rb</code> 文件中的 <code>Article</code> 模型：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class Article &lt; ApplicationRecord
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
end

</pre>
</div>
<p><a class="anchor" id="security"></a></p><h3 id="security">9 安全</h3><p><a class="anchor" id="basic-authentication"></a></p><h4 id="basic-authentication">9.1 基本身份验证</h4><p>现在如果我们把博客放在网上，任何人都能够添加、修改、删除文章或删除评论。</p><p>Rails 提供了一个非常简单的 HTTP 身份验证系统，可以很好地解决这个问题。</p><p>我们需要一种方法来禁止未认证用户访问 <code>ArticlesController</code> 的动作。这里我们可以使用 Rails 的 <code>http_basic_authenticate_with</code> 方法，通过这个方法的认证后才能访问所请求的动作。</p><p>要使用这个身份验证系统，可以在 <code>app/controllers/articles_controller</code> 文件中的 <code>ArticlesController</code> 的顶部进行指定。这里除了 <code>index</code> 和 <code>show</code> 动作，其他动作都要通过身份验证才能访问，为此要像下面这样添加代码：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class ArticlesController &lt; ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", except: [:index, :show]

  def index
    @articles = Article.all
  end

  # 为了行文简洁，省略以下内容

</pre>
</div>
<p>同时只有通过身份验证的用户才能删除评论，为此要在 <code>CommentsController</code>（<code>app/controllers/comments_controller.rb</code>）中像下面这样添加代码：</p><div class="code_container">
<pre class="brush: ruby; gutter: false; toolbar: false">
class CommentsController &lt; ApplicationController

  http_basic_authenticate_with name: "dhh", password: "secret", only: :destroy

  def create
    @article = Article.find(params[:article_id])
    # ...
  end

  # 为了行文简洁，省略以下内容

</pre>
</div>
<p>现在如果我们试着新建文章，就会看到 HTTP 基本身份验证对话框：</p><p><img src="images/getting_started/challenge.png" alt="HTTP 基本身份验证对话框"></p><p>此外，还可以在 Rails 中使用其他身份验证方法。在众多选择中，<a href="https://github.com/plataformatec/devise">Devise</a> 和 <a href="https://github.com/binarylogic/authlogic">Authlogic</a> 是两个流行的 Rails 身份验证扩展。</p><p><a class="anchor" id="other-security-considerations"></a></p><h4 id="other-security-considerations">9.2 其他安全注意事项</h4><p>安全，尤其是 Web 应用的安全，是一个广泛和值得深入研究的领域。关于 Rails 应用安全的更多介绍，请参阅<a href="security.html">Ruby on Rails 安全指南</a>。</p><p><a class="anchor" id="whats-next"></a></p><h3 id="whats-next">10 接下来做什么？</h3><p>至此，我们已经完成了第一个 Rails 应用，请在此基础上尽情修改、试验。</p><p>记住你不需要独自完成一切，在安装和运行 Rails 时如果需要帮助，请随时使用下面的资源：</p>
<ul>
<li>  <a href="http://rails.guide">Ruby on Rails 指南</a>
</li>
<li>  <a href="http://railstutorial-china.org">Ruby on Rails 教程</a>
</li>
<li>  <a href="http://groups.google.com/group/rubyonrails-talk">Ruby on Rails 邮件列表</a>
</li>
<li>  irc.freenode.net 中的 <a href="irc://irc.freenode.net/#rubyonrails">#rubyonrails</a> 频道</li>
</ul>
<p><a class="anchor" id="configuration-gotchas"></a></p><h3 id="configuration-gotchas">11 配置问题</h3><p>在 Rails 中，储存外部数据最好都使用 UTF-8 编码。虽然 Ruby 库和 Rails 通常都能将使用其他编码的外部数据转换为 UTF-8 编码，但并非总是能可靠地工作，所以最好还是确保所有的外部数据都使用 UTF-8 编码。</p><p>编码出错的最常见症状是在浏览器中出现带有问号的黑色菱形块，另一个常见症状是本该出现“ü”字符的地方出现了“Ã¼”字符。Rails 内部采取了许多步骤来解决常见的可以自动检测和纠正的编码问题。尽管如此，如果不使用 UTF-8 编码来储存外部数据，偶尔还是会出现无法自动检测和纠正的编码问题。</p><p>下面是非 UTF-8 编码数据的两种常见来源：</p>
<ul>
<li>  文本编辑器：大多数文本编辑器（例如 TextMate）默认使用 UTF-8 编码保存文件。如果你的文本编辑器未使用 UTF-8 编码，就可能导致在模板中输入的特殊字符（例如 é）在浏览器中显示为带有问号的黑色菱形块。这个问题也会出现在 i18n 翻译文件中。大多数未默认使用 UTF-8 编码的文本编辑器（例如 Dreamweaver 的某些版本）提供了将默认编码修改为 UTF-8 的方法，别忘了进行修改。</li>
<li>  数据库：默认情况下，Rails 会把从数据库中取出的数据转换成 UTF-8 格式。尽管如此，如果数据库内部不使用 UTF-8 编码，就有可能无法保存用户输入的所有字符。例如，如果数据库内部使用 Latin-1 编码，而用户输入了俄语、希伯来语或日语字符，那么在把数据保存到数据库时就会造成数据永久丢失。因此，只要可能，就请在数据库内部使用 UTF-8 编码。</li>
</ul>


        <h3>反馈</h3>
        <p>
          我们鼓励您帮助提高本指南的质量。
        </p>
        <p>
          如果看到如何错字或错误，请反馈给我们。
          您可以阅读我们的<a href="http://edgeguides.rubyonrails.org/contributing_to_ruby_on_rails.html#contributing-to-the-rails-documentation">文档贡献</a>指南。
        </p>
        <p>
          您还可能会发现内容不完整或不是最新版本。
          请添加缺失文档到 master 分支。请先确认 <a href="http://edgeguides.rubyonrails.org">Edge Guides</a> 是否已经修复。
          关于用语约定，请查看<a href="ruby_on_rails_guides_guidelines.html">Ruby on Rails 指南指导</a>。
        </p>
        <p>
          无论什么原因，如果你发现了问题但无法修补它，请<a href="https://github.com/rails/rails/issues">创建 issue</a>。
        </p>
        <p>
          最后，欢迎到 <a href="http://groups.google.com/group/rubyonrails-docs">rubyonrails-docs 邮件列表</a>参与任何有关 Ruby on Rails 文档的讨论。
        </p>
        <h4>中文翻译反馈</h4>
        <p>贡献：<a href="https://github.com/ruby-china/guides">https://github.com/ruby-china/guides</a>。</p>
      </div>
    </div>
  </div>

  <hr class="hide" />
  <div id="footer">
    <div class="wrapper">
      <p>本著作采用 <a href="https://creativecommons.org/licenses/by-sa/4.0/">创作共用 署名-相同方式共享 4.0 国际</a> 授权</p>
<p>“Rails”，“Ruby on Rails”，以及 Rails Logo 为 David Heinemeier Hansson 的商标。版权所有</p>

    </div>
  </div>

  <script type="text/javascript" src="javascripts/jquery.min.js"></script>
  <script type="text/javascript" src="javascripts/responsive-tables.js"></script>
  <script type="text/javascript" src="javascripts/guides.js"></script>
  <script type="text/javascript" src="javascripts/syntaxhighlighter.js"></script>
  <script type="text/javascript">
    syntaxhighlighterConfig = {
      autoLinks: false,
    };
    $(guidesIndex.bind);
  </script>
</body>
</html>
